using System;

namespace CLELCore.Assembly.BlobStreams
{
	public class Blob
	{
		public const int SMALL_BLOB_TYPE = 0x0;
		public const int MEDIUM_BLOB_TYPE = 0x2;
		public const int LARGE_BLOB_TYPE = 0x7;

		public const int MAX_SMALL_SIZE = 127; //2^7-1
		public const int MAX_MEDIUM_SIZE = 16383; //2^14-1
		public const int MAX_LARGE_SIZE = 536870911; //2^29-1

		private byte[] _blob;

		public byte [] BlobValue
		{
			get
			{
				return _blob;
			}
			set
			{
				_blob = value;
			}
		}
		public int Length
		{
			get
			{
				return _blob.Length;
			}
		}

		public Blob(byte [] blob)
		{
			if(blob == null)
				throw new Exception("Error blob cannot be null");
			if(blob.Length == 0)
				throw new Exception("Error blob cannot be empty");
			_blob = blob;
		}

		public static byte [] CompressBlobValue(int val)
		{
			byte [] ret;
			int i;
			if(val <= MAX_SMALL_SIZE)
			{
				ret = new byte[1];
				ret[0] = Bits.ByteFromInt(0,val);
				ret[0] |= (byte)(SMALL_BLOB_TYPE<<7);
			}
			else if(val <= MAX_MEDIUM_SIZE)
			{
				ret = new byte[2];
				for(i = 0;i < 2;i++)
					ret[1-i] = Bits.ByteFromInt(i,val);
				ret[0] |= (byte)(MEDIUM_BLOB_TYPE<<6);
				
			}
			else
			{
				ret = new byte[4];
				for(i = 0;i < 4;i++)
					ret[3-i] = Bits.ByteFromInt(i,val);
				ret[0] |= (byte)(LARGE_BLOB_TYPE<<5);
			}
			return ret;
		}

		public static int DecompressBlobValue(byte[] buffer,ref int start_offset)
		{
			if(buffer == null)
				throw new ArgumentException("Blob cannot be null");
			if(start_offset < 0 || start_offset >= buffer.Length)
				throw new IndexOutOfRangeException("Start offset out of range");
			int type = ((int)buffer[start_offset])& 0xff;
			int ret = 0;
			if(Bits.GetBits(7,7,type) == Blob.SMALL_BLOB_TYPE)
			{
				ret = type;
				start_offset++;
			}
			else if(Bits.GetBits(6,7,type) == Blob.MEDIUM_BLOB_TYPE)
			{
				ret = Bits.MakeInt(buffer[start_offset+1],(byte)Bits.GetBits(0,5,type),0,0);
				start_offset += 2;
			}
			else if(Bits.GetBits(5,7,type) == Blob.LARGE_BLOB_TYPE)
			{
				ret = Bits.MakeInt(buffer[start_offset+3],buffer[start_offset+2],buffer[start_offset+1],(byte)Bits.GetBits(0,4,type));
				start_offset += 4;
			}
			else
			{
				throw new CLELParserException("Error unidenified blob type: " + type + " offset: " + start_offset);
			}
			return ret;
		}

		public static Blob ParseBlob(byte[] buffer,int off)
		{
			byte [] blob = null;
			int i;
			int type_size = 0;
			int len = 0;
			int type = ((int)buffer[off])& 0xff;
			if(Bits.GetBits(7,7,type) == Blob.SMALL_BLOB_TYPE)
			{
				len = type;
				type_size = 1;
			}
			else if(Bits.GetBits(6,7,type) == Blob.MEDIUM_BLOB_TYPE)
			{
				len = Bits.MakeInt(buffer[off+1],(byte)Bits.GetBits(0,5,type),0,0);
				type_size = 2;
			}
			else if(Bits.GetBits(5,7,type) == Blob.LARGE_BLOB_TYPE)
			{
				len = Bits.MakeInt(buffer[off+3],buffer[off+2],buffer[off+1],(byte)Bits.GetBits(0,4,type));
				type_size = 4;
			}
			else
			{
				throw new CLELParserException("Error unidenified blob type: " + type + " offset: " + off);
			}
			int stop = len + type_size;
			blob = new byte[stop];
			if(type_size == 1)
			{
				blob[0] = (byte)type;
			}
			else if(type_size == 2)
			{
				blob[1] = buffer[off+1];
				blob[0] = (byte)type;
			}
			else
			{
				blob[3] = buffer[3];
				blob[2] = buffer[2];
				blob[1] = buffer[1];
				blob[0] = (byte)type;
			}
			for(i = type_size;i < stop;i++)
			{
				blob[i] = buffer[off+i];
			}
			return new Blob(blob);
		}
	}
}
